using DemoContentLoader;
using SharpDX.D3DCompiler;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace DemoContentBuilder
{
    /// <summary>
    /// 存储已编译的着色器及其关联的时间戳,以了解需要新编译哪些着色器。
    /// </summary>
    /// <remarks>This is a bit overkill for the bepuphysics demos, but... I just pulled it over from the graphics engine's pipeline so shrug.</remarks>
    public class ShaderCompilationCache
    {
        /// <summary>
        /// 获取文件名到表示文件上次编译时间的时间戳的映射。
        /// 这包括仅显示为依赖项的文件。
        /// </summary>
        public Dictionary<string, long> TimeStamps { get; private set; }

        /// <summary>
        /// 获取文件名到其先前编译结果的映射。
        /// 仅包括具有入口点的着色器文件。
        /// </summary>
        public Dictionary<SourceShader, ShaderBytecode> CompiledShaders { get; private set; }

        /// <summary>
        /// 获取文件名到它们所依赖的文件的映射。
        /// </summary>
        public Dictionary<string, HashSet<string>> Dependencies { get; private set; }

        /// <summary>
        /// 获取编译此缓存时使用的标志集。
        /// </summary>
        public ShaderFlags ShaderFlags { get; private set; }

        public ShaderCompilationCache(ShaderFlags flags)
        {
            ShaderFlags = flags;
            TimeStamps = new Dictionary<string, long>();
            CompiledShaders = new Dictionary<SourceShader, ShaderBytecode>();
            Dependencies = new Dictionary<string, HashSet<string>>();
        }

        // 着色器内容存档的格式：
        // int32保存着色器标志
        // 存储在int32中的路径着色器计数
        // {路径着色器的连续列表}
        //
        // 其中每个元素表示为：
        // 路径名
        // 着色器字节码大小(以字节为单位)(Int64)
        // 着色器字节码
        // 
        // 存储在int32中的路径时间戳计数
        // {路径时间戳的连续列表}
        //
        // 其中每个元素表示为：
        // 路径名
        // 时间戳
        //
        // 存储在int32中的路径依赖项计数
        // {路径依赖项的连续列表}
        //
        // 其中每个元素表示为：
        // 路径名
        // 依赖项的数量
        // [依赖路径列表]
        //
        // 其中,每个唯一的元素名称由UTF-16字符串表示,存储为：
        // 字符数(Int32)
        // {字符}


        public static void Save(ShaderCompilationCache cache, string path)
        {
            using (var archiveFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                Save(cache, archiveFileStream);
            }
        }

        public static void Save(ShaderCompilationCache cache, Stream outputStream)
        {
            // 保存材质球的数量。
            using (var writer = new BinaryWriter(outputStream))
            {
                writer.Write((int)cache.ShaderFlags);
                writer.Write(cache.CompiledShaders.Count);

                // 按顺序保存每个路径着色器。
                foreach (var element in cache.CompiledShaders)
                {
                    // 写下元素的名称。
                    writer.Write(element.Key.Name);
                    writer.Write(element.Key.Defines.Length);
                    for (int i = 0; i < element.Key.Defines.Length; ++i)
                    {
                        writer.Write(element.Key.Defines[i]);
                    }

                    // 写入着色器字节码的大小。
                    writer.Write(element.Value.Data.Length);
                    // 编写字节码本身。
                    writer.Write(element.Value.Data, 0, element.Value.Data.Length);


                }

                // 保存带时间戳的引用的数量。
                // 请注意,时间戳和依赖项没有定义的排列。相对于排列,时间戳和依赖性不会改变。
                writer.Write(cache.TimeStamps.Count);

                foreach (var element in cache.TimeStamps)
                {
                    writer.Write(element.Key);

                    // 将当前时间戳写入元素。
                    writer.Write(element.Value);
                }

                writer.Write(cache.Dependencies.Count);
                foreach (var pathDependenciesPair in cache.Dependencies)
                {
                    writer.Write(pathDependenciesPair.Key);
                    writer.Write(pathDependenciesPair.Value.Count);
                    foreach (var dependency in pathDependenciesPair.Value)
                    {
                        writer.Write(dependency);
                    }
                }
            }
        }

        public static bool TryLoad(string path, out ShaderCompilationCache cache)
        {
            if (!File.Exists(path))
            {
                cache = null;
                return false;
            }
            using (var stream = File.OpenRead(path))
            {
                return TryLoad(stream, out cache);
            }
        }



        public static bool TryLoad(Stream stream, out ShaderCompilationCache cache)
        {
            using (var reader = new BinaryReader(stream))
            {
                try
                {
                    cache = new ShaderCompilationCache((ShaderFlags)reader.ReadInt32());

                    byte[] data = new byte[16384];

                    var shaderDataCount = reader.ReadInt32();
                    for (int i = 0; i < shaderDataCount; ++i)
                    {
                        var shaderSource = SourceShader.Read(reader);

                        // 读取内容元素数据本身的大小(以字节为单位)。
                        int sizeInBytes = reader.ReadInt32();

                        if (data.Length < sizeInBytes)
                            data = new byte[sizeInBytes];

                        reader.Read(data, 0, sizeInBytes);

                        ShaderBytecode bytecode;
                        unsafe
                        {
                            fixed (byte* buffer = data)
                            {
                                bytecode = new ShaderBytecode(new IntPtr(buffer), sizeInBytes);
                            }
                        }
                        cache.CompiledShaders.Add(shaderSource, bytecode);
                    }

                    var timeStampCount = reader.ReadInt32();
                    for (int i = 0; i < timeStampCount; ++i)
                    {
                        var shaderSource = reader.ReadString();

                        // 看看时间戳。
                        long timeStamp = reader.ReadInt64();
                        cache.TimeStamps.Add(shaderSource, timeStamp);
                    }

                    var dependenciesCount = reader.ReadInt32();
                    for (int i = 0; i < dependenciesCount; ++i)
                    {
                        var shaderSource = reader.ReadString();
                        var pathDependenciesCount = reader.ReadInt32();
                        var dependencies = new HashSet<string>();
                        for (int j = 0; j < pathDependenciesCount; ++j)
                        {
                            dependencies.Add(reader.ReadString());
                        }
                        cache.Dependencies.Add(shaderSource, dependencies);
                    }
                }
                catch
                {
                    cache = null;
                    return false;
                }
                return true;
            }
        }


        public void CopyFrom(string source, ShaderCompilationCache loadedCache)
        {
            lock (CompiledShaders)
            {
                // 每个阶段都试一试就行了。在这件事上可以更聪明些,但是不行。
                foreach (var stage in MetadataParsing.Stages)
                {
                    // 旧的缓存数据仍然有效。将其复制到新缓存中并跳过。
                    // 请注意,由于着色器排列,可能存在多个编译的着色器。
                    string sourceWithExtension = source + stage.Extension;
                    var existingShaders = from pair in loadedCache.CompiledShaders
                                          where pair.Key.Name == sourceWithExtension
                                          select pair;
                    foreach (var entry in existingShaders)
                    {
                        CompiledShaders.Add(entry.Key, entry.Value);
                    }
                }
                TimeStamps[source] = loadedCache.TimeStamps[source];
                var dependencies = new HashSet<string>(loadedCache.Dependencies[source]);
                foreach (var dependency in dependencies)
                {
                    TimeStamps[dependency] = loadedCache.TimeStamps[dependency];
                }
                Dependencies[source] = dependencies;
            }
        }

        public void AddCompiledShader(ShaderCompilationTarget target, List<string> dependencyPaths, CompilationResult result)
        {
            string[] defines = new string[target.ShaderMacros.Length];
            for (int i = 0; i < target.ShaderMacros.Length; ++i)
            {
                defines[i] = target.ShaderMacros[i].Name;
            }
            var sourceShader = new SourceShader { Name = target.Path + target.Stage.Extension, Defines = defines };
            lock (CompiledShaders)
            {
                CompiledShaders[sourceShader] = result;
                TimeStamps[target.Path] = target.CurrentTimeStamp;

                foreach (var dependency in dependencyPaths)
                {
                    TimeStamps[dependency] = File.GetLastWriteTime(dependency).Ticks;
                }
                if (!Dependencies.TryGetValue(target.Path, out HashSet<string> cacheDependencyPaths))
                {
                    cacheDependencyPaths = new HashSet<string>();
                    Dependencies.Add(target.Path, cacheDependencyPaths);
                }
                foreach (var dependencyPath in dependencyPaths)
                {
                    cacheDependencyPaths.Add(dependencyPath);
                }
            }
        }

    }
}
